package tenant_common

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"git.mycaigou.com/gfyx/common/cache"
	"git.mycaigou.com/ycg/stark-extend/starkcommon"
	"git.myscrm.cn/golang/common/gtool"
	"git.myscrm.cn/golang/common/ykenv"
	"git.myscrm.cn/golang/common/ykerrcode"
	"git.myscrm.cn/golang/stark/v4"
	"git.myscrm.cn/golang/stark/v4/config/setting"
	"github.com/gin-gonic/gin"
	"google.golang.org/grpc/metadata"
	"gorm.io/gorm"
)

const (
	CONTEXT_DB_TENANT               = "db"
	TENANT_CODE                     = "tenant_code"
	DEFAULT_TENANT_CODE             = "mysoft"
	DEFAULT_TENANT_DB_NAME          = "modeling_platform"
	CACHE_TENANTDB_CONFIG_CACHE_KEY = "gfyx-tenantdb-config"
	//
	//CONTEXT_DB_CONFIG  = "config_db"
	//CONTEXT_DB_SERVICE = "service_db"
	//CONTEXT_DB_PROJECT = "project_db"
)

var TenantConfigMysqlConn stark.MySQLConnIface

// 事务的上下文key
type txCtxKey struct {
	key string
}

type TenantDB struct {
}

func GetTenantDB(ctx context.Context) *gorm.DB {
	return getDB(ctx, CONTEXT_DB_TENANT)
}

// 获取租户库
func (t *TenantDB) GetTenantDB(ctx context.Context) *gorm.DB {
	return getDB(ctx, CONTEXT_DB_TENANT)
}

// 开启租户db事务
func (t *TenantDB) TenantDBBeginTx(ctx context.Context) (context.Context, *gorm.DB) {
	return transaction(ctx, CONTEXT_DB_TENANT)
}

// 开启租户db事务
func TenantDBBeginTx(ctx context.Context) (context.Context, *gorm.DB) {
	return transaction(ctx, CONTEXT_DB_TENANT)
}

func getDB(ctx context.Context, key string) *gorm.DB {
	// 优先获取事务db
	tx, ok := ctx.Value(txCtxKey{key: key}).(*gorm.DB)
	if ok {
		return tx
	}

	db, ok := ctx.Value(key).(*gorm.DB)
	if ok {
		return db
	}

	return nil
}

// GetTenantDBAndNewContext 获取租户db对象和一个新的上下文
func GetTenantDBAndNewContext(ctx context.Context) (context.Context, *gorm.DB, error) {
	db, err := getTenantDB(ctx)
	if err != nil {
		return ctx, db, err
	}
	newCtx := WithTenantDB(context.Background(), db)

	tenantCode := GetMdTenantCode(ctx)
	if tenantCode == "" {
		tenantCode = GetContextTenantCode(ctx)
	}
	if tenantCode == "" {
		return nil, nil, errors.New("获取租户信息失败")
	}
	newCtx = WithTenantCode(newCtx, tenantCode)

	return newCtx, db, nil
}

// 获取租户db对象
func GetTenantDBAndContext(ctx context.Context) (context.Context, *gorm.DB, error) {
	db, err := getTenantDB(ctx)
	if err != nil {
		return ctx, db, err
	}

	ctx = WithTenantDB(ctx, db)

	return ctx, db, nil
}

// 获取租户db对象 gin
func GetTenantDBAndGinContext(ctx *gin.Context) (*gin.Context, *gorm.DB, error) {
	db, err := getTenantDB(ctx)
	if err != nil {
		return ctx, db, err
	}
	_ = WithTenantDB(ctx, db)
	ctx.Set(CONTEXT_DB_TENANT, db)
	return ctx, db, nil
}

func WithTenantDB(ctx context.Context, db *gorm.DB) context.Context {
	return WithDB(ctx, CONTEXT_DB_TENANT, db)
}
func WithDB(ctx context.Context, key string, db *gorm.DB) context.Context {
	if db == nil {
		return ctx
	}

	db = gtool.WithOrmContext(ctx, db)

	return context.WithValue(ctx, key, db)
}

func WithTenantCode(ctx context.Context, tenantCode string) context.Context {
	return context.WithValue(ctx, TENANT_CODE, tenantCode)
}

func GetContextTenantCode(ctx context.Context) string {
	if value, ok := ctx.Value(TENANT_CODE).(string); ok {
		return value
	}
	return ""
}

func GetMdTenantCode(ctx context.Context) string {
	return GetMD(ctx, TENANT_CODE)
}

func GetMD(ctx context.Context, k string) string {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return ""
	}

	v := md.Get(k)
	if len(v) <= 0 {
		return ""
	}

	return v[0]
}

// 获取租户配置库db连接
func GetTenantConfigMysqlConn() (stark.MySQLConnIface, error) {
	//链接db
	//mysqlSetting := setting.MysqlSetting
	//// todo 配置到阿波罗
	//tenantConfigDBName := "tenant_config_service"
	//mysqlSetting.DBName = tenantConfigDBName
	//return helper.NewMySQLConn(mysqlSetting)
	// 现在使用默认的mysql连接作为租户配置连接
	return stark.MySQLConn, nil
}

// 获取租户配置
func GetTenantConfig(ctx context.Context, tenantCode string) (*TenantConfig, error) {
	var err error
	tenantConfig := &TenantConfig{}
	//如果是mysoft租户，则直接用阿波罗的db配置（因为product表没有mysoft的数据连接信息）
	if tenantCode == DEFAULT_TENANT_CODE {
		tenantConfig.TenantCode = tenantCode
		tenantConfig.OpenPort = ""
		tenantConfig.InnerPort = ""
		tenantConfig.OpenHost = setting.MysqlSetting.Host
		tenantConfig.InnerHost = setting.MysqlSetting.Host
		tenantConfig.Password = setting.MysqlSetting.Password
		tenantConfig.Username = setting.MysqlSetting.UserName
		tenantConfig.DbName = DEFAULT_TENANT_DB_NAME
		return tenantConfig, nil
	}

	err = TenantConfigMysqlConn.Get(ctx).Table(tenantConfig.TableName()).Where("tenant_code = ?", tenantCode).First(tenantConfig).Error
	if err == gorm.ErrRecordNotFound {
		stark.Logger.Error(ctx, "GetTenantConfig 获取不到租户配置"+"tenant_code:"+tenantCode+" err:"+err.Error())
		return nil, ykerrcode.TogRPCError(ykerrcode.CONFIG_ERR, "获取不到租户配置 tenant_code:"+tenantCode+" err:"+err.Error())
	}
	if err != nil {
		stark.Logger.Error(ctx, "GetTenantConfig 获取租户配置错误"+"tenant_code:"+tenantCode+" err:"+err.Error())
		return nil, ykerrcode.TogRPCError(ykerrcode.CONFIG_ERR, "获取租户配置错误 tenant_code:"+tenantCode+" err:"+err.Error())
	}
	//span, ctx := apm.StartSpan(ctx, "db.svc.apollo", "request")
	//defer span.End()
	//
	//settings := serviceSetting{
	//	MaxIdleConns: 2,
	//	MaxOpenConns: 5,
	//	ParseTime:    false,
	//}
	//
	//if serviceName := tenant_metadata.GetStarkServiceName(ctx); serviceName != "" {
	//	if err := getServiceApolloInfo(serviceName, &settings); err != nil {
	//		return nil, ykerrcode.TogRPCError(ykerrcode.GET_TENANT_DB_ERR, err.Error())
	//	}
	//}
	return tenantConfig, nil
}

// CheckEfficientTenantByName 根据公司名称判断企业是否是有效且租期内的租户
func CheckEfficientTenantByName(ctx context.Context, companyName string) (bool, error) {
	var count int64
	err := TenantConfigMysqlConn.Get(ctx).Table(Tenant{}.TableName()).
		Where("name = ? and enabled = 1", companyName).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	if count > 0 {
		return true, nil
	}
	return false, nil
}

func getTenantDB(ctx context.Context) (*gorm.DB, error) {
	tenantCode := GetMdTenantCode(ctx)
	if tenantCode == "" {
		tenantCode = GetContextTenantCode(ctx)
	}
	//tenantCode = "ycg"
	if tenantCode == "" {
		// 上下文获取不到租户号
		stark.Logger.Error(ctx, "tenant_db.GetMdTenantCode is empty(上下文获取不到租户号)")
		return nil, ykerrcode.TogRPCError(ykerrcode.ACCESS_DENIED, "tenant_db.GetMdTenantCode is empty(上下文获取不到租户号)")
	}
	var err error
	var tenantMysqlConn stark.MySQLConnIface
	tenantMysqlConn, err = getTenantMysqlConn(ctx, tenantCode)
	if err != nil {
		stark.Logger.Error(ctx, "tenant_db.getTenantMysqlConn err "+err.Error())
		delCacheTenantConfig(ctx, tenantCode)
		tenantMysqlConn, err = getTenantMysqlConn(ctx, tenantCode)
		if err != nil {
			stark.Logger.Error(ctx, "tenant_db.getTenantMysqlConn 二次连接失败 tenantCode:"+tenantCode+" err:"+err.Error())
			return nil, err
		} else {
			stark.Logger.Info(ctx, "tenant_db.getTenantMysqlConn 二次连接成功 tenantCode:"+tenantCode)
		}
	}
	// 获取租户db句柄对象
	return tenantMysqlConn.Get(ctx), nil
}

func getTenantMysqlConn(ctx context.Context, tenantCode string) (stark.MySQLConnIface, error) {
	var err error
	var tenantConfig *TenantConfig
	readCacheSuccess := false
	cacheRedis, _ := cache.Redis()
	cacheKey := fmt.Sprintf(CACHE_TENANTDB_CONFIG_CACHE_KEY+"-%s", tenantCode)
	_, readCacheSuccess, tenantConfig = getCacheTenantConfig(ctx, cacheRedis, cacheKey, tenantCode)
	if readCacheSuccess == true {
		// none
	} else {
		tenantConfig, err = getTenantConfigAndSetCache(ctx, tenantCode, cacheRedis, cacheKey)
		if err != nil {
			return nil, err
		}
	}
	// 获取租户mysql连接
	return GetTenantMysqlConn(tenantConfig)
}

func getTenantConfigAndSetCache(ctx context.Context, tenantCode string, cacheRedis *cache.CRedis, cacheKey string) (*TenantConfig, error) {
	tenantConfig, err := GetTenantConfig(ctx, tenantCode)
	if err != nil {
		stark.Logger.Errorf(ctx, "tenant_db.GetTenantConfig err tenantCode:%s err:%+v tenantConfig:%+v", tenantCode, err, tenantConfig)
		return nil, err
	}
	setCacheTenantConfig(ctx, tenantConfig, tenantCode, cacheRedis, cacheKey)
	return tenantConfig, nil
}

func delCacheTenantConfig(ctx context.Context, tenantCode string) {
	cacheRedis, _ := cache.Redis()
	cacheKey := fmt.Sprintf(CACHE_TENANTDB_CONFIG_CACHE_KEY+"-%s", tenantCode)
	_, _ = cacheRedis.Del(ctx, cacheKey)
}

func setCacheTenantConfig(ctx context.Context, tenantConfig *TenantConfig, tenantCode string, cacheRedis *cache.CRedis, cacheKey string) {
	// 租户配置放入缓存
	tenantConfigJsonByte, err := json.Marshal(tenantConfig)
	if err != nil {
		stark.Logger.Errorf(ctx, "tenant_db-json.Marshal err tenantCode:%s tenantConfig:%+v err:%+v", tenantCode, tenantConfig, err)
	} else {
		// 存储 JSON 字符串到 Redis，缓存2分钟
		_, err = cacheRedis.Set(ctx, cacheKey, string(tenantConfigJsonByte), true, 2*60)
		if err != nil {
			stark.Logger.Errorf(ctx, "tenant_db-cacheRedis.Set err tenantCode:%s tenantConfig:%+v cacheKey:%s err:%+v", tenantCode, tenantConfig, cacheKey, err)
		}
	}
}

func getCacheTenantConfig(ctx context.Context, cacheRedis *cache.CRedis, cacheKey string, tenantCode string) (error, bool, *TenantConfig) {
	cacheTokenWithVal, err := cacheRedis.Get(ctx, cacheKey)
	if err != nil {
		stark.Logger.Errorf(ctx, "tenant_db-cache get err tenantCode:%s cacheKey:%+v err:%+v", tenantCode, cacheKey, err)
	}
	readCacheSuccess := false
	tenantConfig := &TenantConfig{}
	if cacheTokenWithVal != "" {
		err = json.Unmarshal([]byte(cacheTokenWithVal), &tenantConfig)
		if err != nil {
			readCacheSuccess = false
			stark.Logger.Errorf(ctx, "tenant_db-json.Unmarshal err tenantCode:%s cacheKey:%+v cacheTokenWithVal:%v err:%+v", tenantCode, cacheKey, cacheTokenWithVal, err)
		} else {
			readCacheSuccess = true
		}
	}
	return err, readCacheSuccess, tenantConfig
}

// 获取指定租户的mysql连接
func GetTenantMysqlConn(tenantConfig *TenantConfig) (stark.MySQLConnIface, error) {
	// 不能直接这样用，否则是对象引用！！！//mysqlSetting := setting.MysqlSetting
	mysqlSetting := &setting.MysqlSettingS{
		Host:              setting.MysqlSetting.Host,
		UserName:          setting.MysqlSetting.UserName,
		Password:          setting.MysqlSetting.Password,
		DBName:            setting.MysqlSetting.DBName,
		Charset:           setting.MysqlSetting.Charset,
		MaxIdle:           setting.MysqlSetting.MaxIdle,
		MaxOpen:           setting.MysqlSetting.MaxOpen,
		Loc:               setting.MysqlSetting.Loc,
		ConnMaxLifeSecond: setting.MysqlSetting.ConnMaxLifeSecond,
		MultiStatements:   setting.MysqlSetting.MultiStatements,
		ParseTime:         setting.MysqlSetting.ParseTime,
		Timeout:           setting.MysqlSetting.Timeout,
	}

	// 目前是单实例，应该所有都在一个host里面
	if ykenv.IsDevMode() {
		// 开发环境走外网
		if tenantConfig.OpenPort != "" {
			mysqlSetting.Host = tenantConfig.OpenHost + ":" + tenantConfig.OpenPort
		} else {
			mysqlSetting.Host = tenantConfig.OpenHost
		}
	} else {
		// 内网地址
		if tenantConfig.InnerPort != "" {
			mysqlSetting.Host = tenantConfig.InnerHost + ":" + tenantConfig.InnerPort
		} else {
			mysqlSetting.Host = tenantConfig.InnerHost
		}
	}

	// todo charset选什么?
	mysqlSetting.DBName = tenantConfig.DbName
	mysqlSetting.UserName = tenantConfig.Username
	mysqlSetting.Password = tenantConfig.Password
	return starkcommon.NewMySQLConn(mysqlSetting)
}

func transaction(ctx context.Context, key string) (context.Context, *gorm.DB) {
	db := getDB(ctx, key)
	if db == nil {
		return ctx, nil
	}
	db = db.Begin()
	return contextWithTransaction(ctx, key, db), db
}

func contextWithTransaction(ctx context.Context, key string, tx *gorm.DB) context.Context {
	if tx == nil {
		return ctx
	}
	return context.WithValue(ctx, txCtxKey{key: key}, tx)
}
